🔧 CadQuery Complete Tutorial Guide

Master Parametric 3D Modeling with Python

1. Getting Started with CadQuery

What is CadQuery?

CadQuery is a Python-based parametric CAD library that allows you to build 3D models using code. It's perfect for creating precise, reproducible designs for 3D printing, manufacturing, and engineering applications.

Installation

# Using pip
pip install cadquery

# Using conda (recommended)
conda install -c conda-forge -c cadquery cadquery=master

Your First CadQuery Script

import cadquery as cq

# Create a simple box
result = cq.Workplane("XY").box(10, 10, 5)

# Display or export
show_object(result)  # In CQ-editor

Setting Up Your Environment

Option 1: CQ-Editor

Recommended for beginners

  • Download from GitHub
  • Built-in 3D viewer
  • Code editor included
  • Easy visualization

Option 2: Jupyter Notebook

from jupyter_cadquery import show
show(result)

2. Understanding Workplanes

Workplanes are the foundation of CadQuery. They define the 2D plane where you'll draw shapes that will later become 3D objects.

Basic Workplane Concepts

import cadquery as cq

# Creating workplanes on different faces
wp_xy = cq.Workplane("XY")  # Top view (default)
wp_xz = cq.Workplane("XZ")  # Front view
wp_yz = cq.Workplane("YZ")  # Side view

Moving and Transforming Workplanes

# Move workplane along Z axis
result = (cq.Workplane("XY")
    .center(5, 5)  # Move origin to (5, 5)
    .box(10, 10, 2)
)

# Create workplane at a specific height
result = (cq.Workplane("XY")
    .workplane(offset=10)  # 10 units above XY plane
    .circle(5)
    .extrude(3)
)

Working with Multiple Workplanes

# Select faces to create new workplanes
result = (cq.Workplane("XY")
    .box(20, 20, 10)
    .faces(">Z")  # Select top face
    .workplane()
    .circle(5)
    .cutThruAll()  # Cut a hole through
)

3. Basic 2D Shapes

2D shapes are the building blocks that you'll extrude or revolve into 3D objects.

Rectangles and Boxes

# Center rectangle
rect = cq.Workplane("XY").rect(10, 20)

# Rectangle from corner
rect = cq.Workplane("XY").rect(10, 20, centered=False)

# Rounded rectangle
rounded = cq.Workplane("XY").rect(10, 20).fillet2D(2)

Circles and Ellipses

# Simple circle
circle = cq.Workplane("XY").circle(10)

# Ellipse
ellipse = cq.Workplane("XY").ellipse(15, 10)

# Multiple circles at different points
circles = (cq.Workplane("XY")
    .pushPoints([(0,0), (10,10), (20,0)])
    .circle(3)
)

Polygons

# Regular polygon (hexagon)
hexagon = cq.Workplane("XY").polygon(6, 10)

# Custom polygon from points
custom = (cq.Workplane("XY")
    .moveTo(0, 0)
    .lineTo(10, 0)
    .lineTo(10, 10)
    .lineTo(5, 15)
    .lineTo(0, 10)
    .close()
)

Splines and Complex Shapes

# Spline through points
spline = (cq.Workplane("XY")
    .spline([(0, 0), (5, 5), (10, 3), (15, 8)])
    .close()
)

4. 3D Operations: Extrude and Revolve

Extrusion

Extrusion extends a 2D shape along a perpendicular axis to create 3D geometry.

# Simple extrusion
box = cq.Workplane("XY").rect(10, 10).extrude(5)

# Extrusion with taper
tapered = cq.Workplane("XY").rect(10, 10).extrude(10, taper=15)

# Extrude both directions
result = (cq.Workplane("XY")
    .circle(5)
    .extrude(10, both=True)
)

Revolution

Revolution rotates a 2D profile around an axis to create rotationally symmetric parts.

# Basic revolve - create a sphere
sphere = (cq.Workplane("XZ")
    .circle(10)
    .revolve()
)

# Partial revolve - create a bowl
bowl = (cq.Workplane("XZ")
    .moveTo(5, 0)
    .lineTo(10, 0)
    .lineTo(10, 5)
    .lineTo(5, 8)
    .close()
    .revolve(180)  # Revolve 180 degrees
)

# Create a vase with spline profile
vase = (cq.Workplane("XZ")
    .moveTo(5, 0)
    .spline([(5, 0), (7, 5), (6, 10), (8, 15)])
    .lineTo(0, 15)
    .lineTo(0, 0)
    .close()
    .revolve()
)
Key Concepts:
  • Extrude: Best for prismatic shapes (boxes, channels, beams)
  • Revolve: Perfect for circular parts (bottles, shafts, bowls)
  • Taper: Creates sloped walls (useful for molding)

5. Boolean Operations

Boolean operations combine multiple shapes using union, subtraction, or intersection to create complex geometries.

Union (Combining Shapes)

# Combine two shapes - automatic union
result = (cq.Workplane("XY")
    .box(20, 20, 5)
    .faces(">Z").workplane()
    .circle(5).extrude(10)
)

Subtraction (Cutting)

# Cut a cylinder through a box
result = (cq.Workplane("XY")
    .box(20, 20, 10)
    .faces(">Z").workplane()
    .circle(5).cutThruAll()
)

# Cut to specific depth
result = (cq.Workplane("XY")
    .box(20, 20, 10)
    .faces(">Z").workplane()
    .circle(5).cutBlind(-5)  # Cut 5 units down
)

Intersection

# Create intersection of sphere and box
sphere = cq.Workplane("XY").sphere(10)
box = cq.Workplane("XY").box(15, 15, 15)
result = sphere.intersect(box)

Complex Boolean Example

# Create a bracket with multiple operations
bracket = (cq.Workplane("XY")
    .box(50, 40, 10)  # Main body
    .faces(">Z").workplane()
    .pushPoints([(-15, 0), (15, 0)])
    .circle(5).cutThruAll()  # Mounting holes
    .faces(">Y").workplane()
    .rect(30, 8).cutBlind(-15)  # Side slot
)

6. Adding Details: Fillets and Chamfers

Fillets and chamfers are essential for creating professional-looking designs, improving part strength, and making objects safer to handle.

Understanding Fillets

Fillets are rounded edges that create smooth transitions between surfaces. They reduce stress concentrations and eliminate sharp edges.

# Basic fillet on edges
box = (cq.Workplane("XY")
    .box(20, 20, 10)
    .edges(">Z")
    .fillet(3)
)

Edge Selection Syntax

Common Edge Selectors:
  • "|Z" - Edges parallel to Z axis (vertical edges)
  • ">Z" - Edges on the top face (positive Z direction)
  • "<Z" - Edges on the bottom face (negative Z direction)
  • "|X" - Edges parallel to X axis
  • "|Y" - Edges parallel to Y axis
  • ">X and |Z" - Combine selectors with AND logic
# Multiple fillet sizes on different edges
result = (cq.Workplane("XY")
    .box(30, 20, 10)
    .edges("|Z").fillet(2)
    .edges(">Z").fillet(1)
    .edges("<Z").fillet(0.5)
)

2D Fillets

# Fillet 2D shapes before extruding
profile = (cq.Workplane("XY")
    .rect(30, 20)
    .fillet2D(5)   # Rounds corners in 2D
    .extrude(10)
)

# Combine 2D and 3D fillets
result = (cq.Workplane("XY")
    .rect(40, 30)
    .fillet2D(8)
    .extrude(15)
    .edges("|Z").fillet(2)
)

Understanding Chamfers

Chamfers create angled edges. They're easier to manufacture and help with part assembly.

# Basic 45-degree chamfer
box = (cq.Workplane("XY")
    .box(20, 20, 10)
    .edges(">Z")
    .chamfer(2)
)

# Asymmetric chamfer
result = (cq.Workplane("XY")
    .box(30, 30, 15)
    .edges("|Z")
    .chamfer(3, 1.5)  # Different lengths create angle
)

When to Use Fillets vs Chamfers

✨ Use Fillets

  • Comfortable handling required
  • Reducing stress concentrations
  • Premium aesthetic appearance
  • Improving fluid flow
  • Consumer products

⚡ Use Chamfers

  • Easing part assembly
  • Safety edge removal
  • Easier manufacturing
  • Creating visual breaks
  • Industrial applications
⚠️ Warning: If a fillet radius is too large for the geometry, the operation will fail. The maximum fillet radius is roughly half the smallest dimension you're filleting. Start with smaller radii and increase as needed.

7. Working with Holes and Patterns

Creating Holes

# Simple through hole
result = (cq.Workplane("XY")
    .box(20, 20, 5)
    .faces(">Z").workplane()
    .hole(6)  # 6mm diameter
)

# Countersink hole (for flat head screws)
result = (cq.Workplane("XY")
    .box(20, 20, 5)
    .faces(">Z").workplane()
    .cskHole(6, 12, 82)  # hole dia, csink dia, angle
)

# Counterbore hole (for socket head screws)
result = (cq.Workplane("XY")
    .box(20, 20, 5)
    .faces(">Z").workplane()
    .cboreHole(6, 10, 3)  # hole dia, cbore dia, depth
)

Linear Patterns

# Rectangular array of holes
result = (cq.Workplane("XY")
    .box(50, 50, 5)
    .faces(">Z").workplane()
    .rarray(10, 10, 4, 4)  # xSpace, ySpace, xCount, yCount
    .circle(2).cutThruAll()
)

# Linear pattern along one axis
result = (cq.Workplane("XY")
    .box(50, 20, 5)
    .faces(">Z").workplane()
    .rarray(10, 1, 5, 1)
    .circle(2).cutThruAll()
)

Circular Patterns

# Polar array (evenly spaced around circle)
result = (cq.Workplane("XY")
    .circle(30).extrude(5)
    .faces(">Z").workplane()
    .polarArray(20, 0, 360, 8)  # radius, start, angle, count
    .circle(3).cutThruAll()
)

Custom Point Patterns

# Holes at specific coordinates
points = [(0, 0), (10, 10), (-10, 10), (10, -10), (-10, -10)]
result = (cq.Workplane("XY")
    .box(40, 40, 5)
    .faces(">Z").workplane()
    .pushPoints(points)
    .circle(3).cutThruAll()
)

8. Assemblies and Multiple Parts

Creating Simple Assemblies

from cadquery import Assembly

# Create individual parts
base = cq.Workplane("XY").box(50, 50, 10)
post = cq.Workplane("XY").circle(5).extrude(30)

# Create assembly
assy = (Assembly()
    .add(base, name="base", color=cq.Color("gray"))
    .add(post, name="post", 
         loc=cq.Location(cq.Vector(0, 0, 10)),
         color=cq.Color("blue"))
)

Positioning Parts

# Using Location for precise positioning
part1 = cq.Workplane("XY").box(20, 20, 10)
part2 = cq.Workplane("XY").box(10, 10, 20)

assy = Assembly()
assy.add(part1, name="base")
assy.add(part2, name="tower",
         loc=cq.Location(cq.Vector(15, 0, 5)))

Exporting Assemblies

# Save as STEP file (preserves assembly structure)
assy.save("assembly.step")

9. Parametric Design Techniques

Parametric design uses variables to create flexible, reusable models that can be easily modified.

Basic Parametric Model

# Define parameters
width = 50
height = 30
thickness = 5
hole_diameter = 8

# Create parametric plate
plate = (cq.Workplane("XY")
    .box(width, height, thickness)
    .faces(">Z").workplane()
    .pushPoints([(-width/3, 0), (width/3, 0)])
    .circle(hole_diameter/2).cutThruAll()
)

Using Functions for Reusability

def create_mounting_plate(width, height, thickness, hole_dia, spacing):
    """Create a parametric mounting plate"""
    return (cq.Workplane("XY")
        .box(width, height, thickness)
        .faces(">Z").workplane()
        .pushPoints([(-spacing/2, 0), (spacing/2, 0)])
        .circle(hole_dia/2).cutThruAll()
        .edges("|Z").fillet(2)
    )

# Generate different sizes instantly
plate1 = create_mounting_plate(50, 30, 5, 6, 30)
plate2 = create_mounting_plate(80, 40, 8, 10, 60)
Benefits of Parametric Design:
  • Easy to create design variations
  • Automatic updates when parameters change
  • Reusable code for similar parts
  • Perfect for product families
  • Rapid design iteration

10. Exporting for 3D Printing

STL Export

import cadquery as cq

# Create your model
model = cq.Workplane("XY").box(20, 20, 10)

# Export as STL (standard for 3D printing)
cq.exporters.export(model, "model.stl")

# Higher resolution export
cq.exporters.export(model, "model_hires.stl", tolerance=0.01)

STEP Export

# STEP format preserves exact geometry for CAD
cq.exporters.export(model, "model.step")

3D Printing Best Practices

def printable_box(width, height, depth, wall=2):
    """Create a hollow box optimized for printing"""
    return (cq.Workplane("XY")
        .box(width, height, depth)
        .shell(-wall)  # Hollow with wall thickness
        .edges("|Z").fillet(wall * 0.5)
    )

print_ready = printable_box(50, 50, 30, wall=2)
cq.exporters.export(print_ready, "box.stl")